home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 7 / Apprentice-Release7.iso / Source Code / C / Frameworks / Hsoi's App Shell 1.0a4 / HAS Other Source / WASTE 1.3a4 Distribution / WASTE 1.3a4 / Source / WESelecting.c < prev    next >
Encoding:
C/C++ Source or Header  |  1997-04-14  |  56.8 KB  |  2,284 lines  |  [TEXT/CWIE]

  1. /*
  2.  *    WESelecting.c
  3.  *
  4.  *    WASTE PROJECT
  5.  *  Drawing Selections, Activating, Updating, Scrolling, etc.
  6.  *
  7.  *  Copyright (c) 1993-1997 Marco Piovanelli
  8.  *    All Rights Reserved
  9.  *
  10.  *  C port by Dan Crevier
  11.  *
  12.  */
  13.  
  14.  
  15. #include "WASTEIntf.h"
  16.  
  17. // values for _WEArrowOffset action parameter:
  18.  
  19. enum
  20. {
  21.     // plain arrow keys
  22.     kGoLeft            =    0,
  23.     kGoRight        =    1,
  24.     kGoUp            =    2,
  25.     kGoDown            =    3,
  26.  
  27.     // modifiers
  28.     kOption            =    4,
  29.     kCommand        =    8,
  30.  
  31.     // option + arrow combos
  32.     kGoWordStart    =    kGoLeft + kOption,
  33.     kGoWordEnd        =    kGoRight + kOption,
  34.     kGoTextStart    =    kGoUp + kOption,
  35.     kGoTextEnd        =    kGoDown + kOption,
  36.  
  37.     // command + arrow combos
  38.     kGoLineStart    =    kGoLeft + kCommand,
  39.     kGoLineEnd        =    kGoRight + kCommand,
  40.     kGoPageStart    =    kGoUp + kCommand,
  41.     kGoPageEnd        =    kGoDown + kCommand
  42. };
  43.  
  44. INLINE pascal void _WEClearHiliteBit(void)
  45. {
  46.     LMSetHiliteMode(LMGetHiliteMode() & 0x7F);
  47. }
  48.  
  49. static Boolean SLPixelToChar
  50.     (
  51.         WELineRec *pLine,
  52.         const WERunAttributes *pAttrs,
  53.         Ptr pSegment,
  54.         SInt32 segmentStart,
  55.         SInt32 segmentLength,
  56.         JustStyleCode styleRunPosition,
  57.         WEHandle hWE,
  58.         void *callbackData
  59.     )
  60. {
  61.     struct SLPixelToCharData *cd = (struct SLPixelToCharData *) callbackData;
  62.     WEPtr pWE = *hWE;
  63.     Fixed slop;
  64.     SInt16 cType;
  65.     Fixed oldWidth;
  66. #if WASTE_OBJECTS
  67.     Fixed objectWidth;
  68.     Fixed subWidth;
  69. #endif
  70.  
  71.     // if this is the first style run on the line, subtract pen indent from pixelWidth
  72.     if (IS_LEFTMOST_RUN(styleRunPosition))
  73.     {
  74.         cd->pixelWidth -= BSL(_WECalcPenIndent(pLine, pWE->alignment, pWE->direction), 16);
  75.     }
  76.  
  77.     // if pixelWidth is gone negative already, the point is on the
  78.     // _leading_ edge (NOTE: was trailing in WASTE 1.2) of first glyph
  79.     if (cd->pixelWidth <= 0)
  80.     {
  81.         cd->offset = segmentStart;
  82.         cd->edge = kLeadingEdge;
  83.         return true;    // stop looping
  84.     }
  85.  
  86.     oldWidth = cd->pixelWidth;
  87.  
  88. #if WASTE_OBJECTS
  89.     if (pAttrs->runStyle.tsObject != nil)
  90.     {
  91.  
  92.         // EMBEDDED OBJECT
  93.         // calculate object width as Fixed
  94.         objectWidth = BSL((*pAttrs->runStyle.tsObject)->objectSize.h, 16);
  95.  
  96.         // subtract object width from pixelWidth
  97.         cd->pixelWidth -= objectWidth;
  98.  
  99. #if WASTE_OBJECTS_ARE_GLYPHS
  100.  
  101.         // find out whether the point is in the leftmost half of the object,
  102.         // in the rightmost half or past the object
  103.         subWidth = objectWidth >> 1;    // divide by two
  104.         if (cd->pixelWidth + subWidth < 0)
  105.         {
  106.             cd->offset = segmentStart;
  107.             cd->edge = kLeadingEdge;        // point is in leftmost half of object
  108.         }
  109.         else
  110.         {
  111.             cd->offset= segmentStart + 1;
  112.             if (cd->pixelWidth < 0)
  113.             {
  114.                 cd->edge = kTrailingEdge;
  115.             }    // point is in rightmost half of object
  116.             else
  117.             {
  118.                 cd->edge = kLeadingEdge;    // point is past object
  119.             }
  120.         }
  121. #else
  122.  
  123.         // find out whether the point is in the leftmost quarter of the object,
  124.         // in the middle half, in the rightmost quarter or past the object
  125.         subWidth = objectWidth >> 2;    // divide by four
  126.         if (cd->pixelWidth + subWidth < 0)
  127.         {
  128.             cd->offset = segmentStart;
  129.             if (cd->pixelWidth + objectWidth < subWidth)
  130.             {
  131.                 cd->edge = kLeadingEdge;    // point is in leftmost quarter of object
  132.             }
  133.             else
  134.             {
  135.                 cd->edge = kObjectEdge;        // point is in middle half of object
  136.             }
  137.         }
  138.         else
  139.         {
  140.             cd->offset = segmentStart + 1;
  141.             if (cd->pixelWidth < 0)
  142.             {
  143.                 cd->edge = kTrailingEdge;    // point is in rightmost quarter of object
  144.             }
  145.             else
  146.             {
  147.                 cd->edge = kLeadingEdge;        // point is past object
  148.             }
  149.         }
  150. #endif // WASTE_OBJECTS_ARE_GLYPHS
  151.     }
  152.     else
  153. #endif // WASTE_OBJECTS
  154.     {
  155.  
  156.         // REGULAR TEXT
  157.  
  158.         // if this is the last segment on the line, strip the last blank character (if any),
  159.         // unless it is the last non-CR character in the whole text stream
  160.         if (IS_RIGHTMOST_RUN(styleRunPosition))
  161.         {
  162.             if ((segmentStart + segmentLength < pWE->textLength) ||
  163.                 pSegment[segmentLength - 1] == kEOL)
  164.             {
  165.                 cType = CallWECharTypeProc(pSegment, segmentLength - 1, FontScript(),
  166.                      hWE, pWE->charTypeHook);
  167.                 if ((cType & (smcTypeMask + smcClassMask)) == smCharPunct + smPunctBlank)
  168.                 {
  169.                     segmentLength -= ((cType & smcDoubleMask) ? 2 : 1);
  170.                 }
  171.             }
  172.         }
  173.  
  174.         // calculate slop for this text segment (justified text only)
  175.         if (pWE->alignment == weJustify)
  176.         {
  177.             slop = FixMul(PortionLine(pSegment, segmentLength, styleRunPosition,
  178.                     kOneToOneScaling, kOneToOneScaling), pLine->lineJustAmount);
  179.         }
  180.         else
  181.         {
  182.             slop = 0;
  183.         }
  184.  
  185.         // call PixelToChar hook for this segment
  186.         cd->offset = segmentStart + CallWEPixelToCharProc(pSegment, segmentLength,
  187.                 slop, &cd->pixelWidth, &cd->edge, styleRunPosition, cd->hPos,
  188.                 hWE, pWE->pixelToCharHook);
  189.     }
  190.  
  191.     // increment hPos by change in pixelWidth
  192.     cd->hPos += (oldWidth - cd->pixelWidth);
  193.  
  194.     // if pixelWidth has gone negative, we're finished; otherwise go to next run
  195.     return (cd->pixelWidth <= 0);
  196. }
  197.  
  198. pascal SInt32 WEGetOffset(const LongPt *thePoint, WEEdge *edge, WEHandle hWE)
  199. {
  200.     // given a long point in local coordinates,
  201.     // find the text offset corresponding to the nearest glyph
  202.  
  203.     WEPtr pWE;
  204.     SInt32 lineIndex;
  205.     LongPt tempPoint;
  206.     struct SLPixelToCharData cd;
  207.     Boolean saveWELock;
  208.  
  209.     // lock the WE record
  210.     saveWELock = _WESetHandleLock((Handle) hWE, true);
  211.     pWE = *hWE;
  212.  
  213.     // tempPoint is thePoint, relative to the top left corner of the dest rect
  214.     tempPoint.v = thePoint->v - pWE->destRect.top;
  215.     tempPoint.h = thePoint->h - pWE->destRect.left;
  216.  
  217.     // if the point is above the destination rect, return zero
  218.     if (tempPoint.v < 0)
  219.     {
  220.         cd.offset = 0;
  221.         cd.edge = kTrailingEdge;
  222.     }
  223.     else
  224.     {
  225.         // if the point is below the last line, return last char offset
  226.         if (tempPoint.v >= WEGetHeight(0, LONG_MAX, hWE))
  227.         {
  228.             cd.offset = pWE->textLength;
  229.             cd.edge = kLeadingEdge;
  230.         }
  231.         else
  232.         {
  233.             // find the line index corresponding to the vertical pixel offset
  234.             lineIndex = _WEPixelToLine(tempPoint.v, hWE);
  235.  
  236.             // express the horizontal pixel offset as a Fixed value
  237.             cd.pixelWidth = BSL(tempPoint.h, 16);
  238.  
  239.             // walk through the segments on this line calling PixelToChar
  240.             cd.hPos = 0;
  241.             cd.offset = 0;
  242.             _WESegmentLoop(lineIndex, lineIndex, SLPixelToChar, &cd, hWE);
  243.         }
  244.     }
  245.  
  246.     // unlock the WE record
  247.     _WESetHandleLock((Handle) hWE, saveWELock);
  248.  
  249.     // return offset/edge pair
  250.     if (edge != nil)
  251.     {
  252.         *edge = cd.edge;
  253.     }
  254.     return cd.offset;
  255. }
  256.  
  257. static Boolean SLCharToPixel
  258.     (
  259.         WELineRec *pLine,
  260.         const WERunAttributes *pAttrs,
  261.         Ptr pSegment,
  262.         SInt32 segmentStart,
  263.         SInt32 segmentLength,
  264.         JustStyleCode styleRunPosition,
  265.         WEHandle hWE,
  266.         void *callbackData
  267.     )
  268. {
  269.     struct SLCharToPixelData *cd = (struct SLCharToPixelData *) callbackData;
  270.     WEPtr pWE = *hWE;
  271.     SInt32 offset;
  272.     Fixed slop;
  273.     SInt16 direction = cd->direction;
  274.     SInt16 width;
  275.     Boolean isInSegment;
  276.  
  277.     // calculate offset relative to beginning of segment
  278.     offset = cd->offset - segmentStart;
  279.  
  280.     // is offset within this segment?
  281.     isInSegment = ((offset >= 0) && (offset < segmentLength));
  282.  
  283.     // if this is the first style run on the line, add pen indent to thePoint.h
  284.     if (IS_LEFTMOST_RUN(styleRunPosition))
  285.     {
  286.         cd->thePoint->h += _WECalcPenIndent(pLine, pWE->alignment, pWE->direction);
  287.     }
  288.  
  289. #if WASTE_OBJECTS
  290.     if (pAttrs->runStyle.tsObject != nil)
  291.     {
  292.  
  293.         // EMBEDDED OBJECT
  294.         width = isInSegment ? 0 : (*pAttrs->runStyle.tsObject)->objectSize.h;
  295.     }
  296.     else
  297. #endif
  298.     {
  299.         // REGULAR TEXT
  300.         slop = 0;
  301.  
  302.         // calculate slop for this text segment (justified text only)
  303.         if (pWE->alignment == weJustify)
  304.         {
  305.             slop = FixMul(PortionLine(pSegment, segmentLength, styleRunPosition,
  306.                 kOneToOneScaling, kOneToOneScaling), pLine->lineJustAmount);
  307.         }
  308.  
  309.         if (BTST(pWE->flags, weFBidirectional))
  310.         {
  311.             //    SPECIAL TREATMENT FOR BIDIRECTIONAL SCRIPTS
  312.             Boolean    segmentDir = (BTST(pAttrs->runStyle.tsFlags, tsRightToLeft) != 0);
  313.  
  314.             if ((offset < 0) || (offset > segmentLength))
  315.             {
  316.                 // we want the total width of this segment, so set offset and direction appropriately
  317.                 if (segmentDir)
  318.                 {
  319.                     offset = 0;
  320.                     direction = rightCaret;
  321.                 }
  322.                 else
  323.                 {
  324.                     offset = segmentLength;
  325.                     direction = leftCaret;
  326.                 }
  327.             }
  328.             else if (cd->rightEdge)
  329.             {
  330.                 // we're drawing a cursor at the right edge of the line, so ensure we measure this whole segment
  331.                 isInSegment = false;
  332.                 if (cd->lineDir && !segmentDir)
  333.                 {
  334.                     offset = segmentLength;
  335.                     direction = leftCaret;
  336.                 }
  337.                 else if (!cd->lineDir && segmentDir)
  338.                 {
  339.                     offset = 0;
  340.                     direction = rightCaret;
  341.                 }
  342.             }
  343.             else if ((offset == 0) || (offset == segmentLength))
  344.             {
  345.                 isInSegment = false;
  346.                 // we're at a style run boundary
  347.                 if (((direction == leftCaret) && (! segmentDir)) || ((direction == rightCaret) && segmentDir))
  348.                 {
  349.                     // requested caret direction matches style run direction, so we can exit the loop
  350.                     isInSegment = true;
  351.                 }
  352.                 else
  353.                 {
  354.                     // need caret for direction opposite to this run, so need to check for direction run boundary;
  355.                     // determine whether preceding and following runs are R-L
  356.                     Boolean prevDir;
  357.                     Boolean nextDir;
  358.  
  359.                     //    note that if segmentStart is 0, WEGetRunDirection
  360.                     //    will return the primary line direction
  361.                     prevDir = WEGetRunDirection(segmentStart - 1, hWE);
  362.  
  363.                     //    again, if segmentStart + segmentLength == pWE->textLength,
  364.                     //    WEGetRunDirection will do the Right Thing
  365.                     nextDir = WEGetRunDirection(segmentStart + segmentLength, hWE);
  366.  
  367.                     if ((offset == 0) && (prevDir != segmentDir))
  368.                     {
  369.                         if (cd->lineDir)
  370.                         {
  371.                             if (segmentDir)
  372.                             {
  373.                                 //offset = 0;    // already known to be zero
  374.                                 direction = rightCaret;
  375.                             }
  376.                             else
  377.                             {
  378.                                 offset = segmentLength;
  379.                                 direction = leftCaret;
  380.                             }
  381.                         }
  382.                     }
  383.                     else if ((offset == segmentLength) && (nextDir != segmentDir))
  384.                     {
  385.                         if (!cd->lineDir)
  386.                         {
  387.                             if (segmentDir)
  388.                             {
  389.                                 offset = 0;
  390.                                 direction = rightCaret;
  391.                             }
  392.                             else
  393.                             {
  394.                                 //offset = segmentLength;
  395.                                 direction = leftCaret;
  396.                             }
  397.                         }
  398.                     }
  399.                     else
  400.                     {
  401.                         // not a direction boundary, so force caret dir to match style run dir, and exit loop
  402.                         direction = segmentDir ? rightCaret : leftCaret;
  403.                         isInSegment = true;
  404.                     }
  405.                 }
  406.             }
  407.         } // end of special treatment for bidirectional scripts
  408.  
  409.         // call CharToPixel to get width of segment up to specified offset
  410.         width = CallWECharToPixelProc(pSegment, segmentLength,
  411.             slop, offset, direction, styleRunPosition, cd->thePoint->h,
  412.             hWE, pWE->charToPixelHook);
  413.     }
  414.  
  415.     // advance thePoint.h by the width of this segment
  416.     cd->thePoint->h += width;
  417.  
  418.     // drop out of loop when we reach offset
  419.     return isInSegment;
  420. }
  421.  
  422. pascal void WEGetPoint(SInt32 offset, SInt16 direction, LongPt *thePoint, SInt16 *lineHeight, WEHandle hWE)
  423. {
  424.     // given a byte offset into the text, find the corresponding glyph position
  425.     // this routine is useful for highlighting the text and for positioning the caret
  426.  
  427.     WEPtr pWE;
  428.     WELineRec *pLine;
  429.     SInt32 lineIndex;
  430.     SInt32 height;
  431.     struct SLCharToPixelData cd;
  432.     Boolean saveWELock;
  433.  
  434.     // lock the WE record
  435.     saveWELock = _WESetHandleLock((Handle) hWE, true);
  436.     pWE = *hWE;
  437.  
  438.     // the base point is the top left corner of the destination rectangle
  439.     *thePoint = * (LongPt *) &pWE->destRect;
  440.  
  441.     // first of all find the line on which the glyph lies
  442.     lineIndex = WEOffsetToLine(offset, hWE);
  443.  
  444.     // calculate the vertical coordinate and the line height
  445.     pLine = *pWE->hLines + lineIndex;
  446.     thePoint->v += pLine->lineOrigin;
  447.     height = pLine[1].lineOrigin - pLine[0].lineOrigin;
  448.  
  449.     if ((offset == pWE->textLength) && (WEGetChar(offset - 1, hWE) == kEOL))
  450.     {
  451.         // SPECIAL CASE: if offset is past the last character and
  452.         // the last character is a carriage return, return a point below the last line
  453.         WELineRec dummyLine;
  454.  
  455.         dummyLine.lineSlop = pWE->destRect.right - pWE->destRect.left;
  456.         dummyLine.lineJustAmount = 0;
  457.  
  458.         thePoint->v += height;
  459.         thePoint->h += _WECalcPenIndent(&dummyLine, pWE->alignment, pWE->direction);
  460.     }
  461.     else
  462.     {
  463. // ••• new version of this: better boundary behavior
  464.         SInt32    lineStart, lineEnd;
  465.         Boolean    lineDir;
  466.  
  467.         // determine the dominant line direction
  468.         lineDir = IsRightToLeft(pWE->direction);
  469.  
  470.         // simplify direction so it's either leftCaret or rightCaret
  471.         if (direction == hilite)
  472.         {
  473.             direction = (lineDir ? rightCaret : leftCaret);
  474.         }
  475.  
  476.         // find ends of line so we can special-case them
  477.         WEGetLineRange(lineIndex, &lineStart, &lineEnd, hWE);
  478.         if (lineEnd < pWE->textLength || WEGetChar(lineEnd - 1, hWE) == kEOL)
  479.         {
  480.             --lineEnd;
  481.         }
  482.  
  483.         if ((offset == lineStart && !lineDir && direction == leftCaret)
  484.             || (offset == lineEnd && lineDir && direction == rightCaret)
  485.            )
  486.         {
  487.             // SPECIAL CASE: if caret should be at left end of line, don't call style run loop
  488.             thePoint->h += _WECalcPenIndent(pLine, pWE->alignment, pWE->direction);
  489.         }
  490.         else
  491.         {
  492.             cd.lineDir = lineDir;
  493.             // Inform SLCharToPixel if we want the right edge of the line
  494.             cd.rightEdge = ((offset == lineEnd && !lineDir && direction == leftCaret) ||
  495.                             (offset == lineStart && lineDir && direction == rightCaret));
  496.             cd.offset = offset;
  497.             cd.thePoint = thePoint;
  498.             cd.direction = direction;
  499.  
  500.             // to get the horizontal coordinate, walk through the style runs on this line
  501.             _WESegmentLoop(lineIndex, lineIndex, SLCharToPixel, &cd, hWE);
  502.         }
  503.  
  504. // ••• replaced with code above
  505. /*
  506.         cd.offset = offset;
  507.         cd.thePoint = thePoint;
  508.         cd.direction = direction;
  509.  
  510.         // to get the horizontal coordinate, walk through the style runs on this line
  511.         _WESegmentLoop(lineIndex, lineIndex, SLCharToPixel, &cd, hWE);
  512. */
  513.     }
  514.  
  515.     // pin the horizontal coordinate to the destination rectangle
  516.     thePoint->h = _WEPinInRange(thePoint->h, pWE->destRect.left, pWE->destRect.right);
  517.  
  518.     // unlock the WE record
  519.     _WESetHandleLock((Handle) hWE, saveWELock);
  520.  
  521.     // copy line height
  522.     if (lineHeight != nil)
  523.     {
  524.         *lineHeight = height;
  525.     }
  526. }
  527.  
  528. pascal void WEFindLine(SInt32 offset, WEEdge edge, SInt32 *lineStart, SInt32 *lineEnd, WEHandle hWE)
  529. {
  530. #pragma unused(edge)
  531.     WEPtr pWE = *hWE;
  532.     WELineRec *pLine;
  533.  
  534.     pLine = *pWE->hLines + WEOffsetToLine(offset, hWE);
  535.     if (lineStart != nil)
  536.     {
  537.         *lineStart = pLine[0].lineStart;
  538.     }
  539.     if (lineEnd != nil)
  540.     {
  541.         *lineEnd = pLine[1].lineStart;
  542.     }
  543. }
  544.  
  545. pascal void WEFindParagraph(SInt32 offset, WEEdge edge, SInt32 *paragraphStart, SInt32 *paragraphEnd, WEHandle hWE)
  546. {
  547. #pragma unused(edge)
  548.     WEPtr pWE = * hWE ;
  549.     Ptr pText = * pWE -> hText ;
  550.     SInt32 textLength = pWE -> textLength ;
  551.     WELineRec * pStartLine = * pWE -> hLines + WEOffsetToLine ( offset, hWE ) ;
  552.     WELineRec * pLine ;
  553.  
  554.     //    scan backwards the line array, looking for a line ending with a CR
  555.     for (
  556.             pLine = pStartLine ;
  557.             ( pLine -> lineStart > 0 ) && ( pText [ pLine -> lineStart - 1 ] != kEOL ) ;
  558.             pLine --
  559.         ) ;
  560.     if ( paragraphStart != nil )
  561.     {
  562.         * paragraphStart = pLine -> lineStart ;
  563.     }
  564.  
  565.     //    now scan forward
  566.     for (
  567.             pLine = pStartLine + 1 ;
  568.             ( pLine -> lineStart < textLength ) && ( pText [ pLine -> lineStart - 1 ] != kEOL ) ;
  569.             pLine ++
  570.         ) ;
  571.     if ( paragraphEnd != nil )
  572.     {
  573.         * paragraphEnd = pLine -> lineStart ;
  574.     }
  575. }
  576.  
  577. pascal ScriptCode _WEGetContext(SInt32 offset, SInt32 *contextStart, SInt32 *contextEnd,
  578.                         WEHandle hWE)
  579. {
  580.     // This function finds a range of characters ("context"), all belonging to the same script
  581.     // and centered around the specified offset.
  582.     // The function result is the ID of a font belonging to this script.
  583.     // Ideally, the context should consist of a whole script run, but in practice the returned
  584.     // context can be narrower, for performance and other reasons (see below)
  585.  
  586.     SInt32 index, saveIndex, saveRunEnd;
  587.     WERunInfo runInfo;
  588.     ScriptCode script1, script2;
  589.     SInt16 retval;
  590.  
  591.     if (BTST((*hWE)->flags, weFNonRoman))
  592.     {
  593.         // if more than one script is installed, limit the search of
  594.         // script run boundaries to a single paragraph, for speed's sake
  595.         WEFindParagraph(offset, kLeadingEdge, contextStart, contextEnd, hWE);
  596.  
  597.         // find the style run the specified offset is in
  598.         index = WEOffsetToRun(offset, hWE);
  599.         _WEGetIndStyle(index, &runInfo, hWE);
  600.  
  601.         // find the script code associated with this style run
  602.         script1 = FontToScript(runInfo.runAttrs.runStyle.tsFont);
  603.  
  604.         // the script code is returned as function result
  605.         retval = script1;
  606.  
  607.         // save index and runInfo.runEnd for the second while loop
  608.         saveIndex = index;
  609.         saveRunEnd = runInfo.runEnd;
  610.  
  611.         // walk backwards across style runs preceding offset, looking for a script run boundary
  612.         while (runInfo.runStart > *contextStart)
  613.         {
  614.             index--;
  615.             _WEGetIndStyle(index, &runInfo, hWE);
  616.             script2 = FontToScript(runInfo.runAttrs.runStyle.tsFont);
  617.             if (script1 != script2)
  618.             {
  619.                 *contextStart = runInfo.runEnd;
  620.                 break;
  621.             }
  622.         }
  623.  
  624.         // restore index and runInfo.runEnd
  625.         index = saveIndex;
  626.         runInfo.runEnd = saveRunEnd;
  627.  
  628.         // walk forward across style runs following offset, looking for a script run boundary
  629.         while (runInfo.runEnd < *contextEnd)
  630.         {
  631.             index++;
  632.             _WEGetIndStyle(index, &runInfo, hWE);
  633.             script2 = FontToScript(runInfo.runAttrs.runStyle.tsFont);
  634.             if (script1 != script2)
  635.             {
  636.                 *contextEnd = runInfo.runStart;
  637.                 break;
  638.             }
  639.         }
  640.     }
  641.     else
  642.     {
  643.         // only the Roman script is enabled: the whole text constitutes one script run
  644.         retval = smRoman;
  645.         *contextStart = 0;
  646.         *contextEnd = (*hWE)->textLength;
  647.     }
  648.  
  649.     // make sure the range identified by contextStart/contextEnd is contained within
  650.     // the 32K byte range centered around the specified offset
  651.     // the reason for this is that many Script Manager routines (e.g. FindWord and CharByte)
  652.     // only accept 16-bit offsets, rather than 32-bit offsets
  653.  
  654.     *contextStart = _WEPinInRange(*contextStart, offset - (SHRT_MAX / 2), offset);
  655.     *contextEnd = _WEPinInRange(*contextEnd, offset, offset + (SHRT_MAX / 2));
  656.  
  657.     return retval;
  658. }
  659.  
  660. pascal ScriptCode _WEGetRestrictedContext(SInt32 offset, SInt32 *contextStart, SInt32 *contextEnd,
  661.                         WEHandle hWE)
  662. {
  663.     // This function finds a range of characters ("context"), all belonging to the same script
  664.     // and centered around the specified offset.
  665.     // This function returns a script run subrange and is more efficient than
  666.     // _WEGetContext because it doesn't try to find the script boundaries accurately.
  667.  
  668.     WERunInfo runInfo;
  669.  
  670.     // just find the style run the specified offset is in
  671.     WEGetRunInfo(offset, &runInfo, hWE);
  672.     *contextStart = runInfo.runStart;
  673.     *contextEnd = runInfo.runEnd;
  674.  
  675.     // make sure the range identified by contextStart/contextEnd is contained within
  676.     // the 32K byte range centered around the specified offset
  677.     // the reason for this is that many Script Manager routines (e.g. FindWord and CharByte)
  678.     // only accept 16-bit offsets, rather than 32-bit offsets
  679.  
  680.     *contextStart = _WEPinInRange(*contextStart, offset - (SHRT_MAX / 2), offset);
  681.     *contextEnd = _WEPinInRange(*contextEnd, offset, offset + (SHRT_MAX / 2));
  682.  
  683.     return FontToScript(runInfo.runAttrs.runStyle.tsFont);
  684. }
  685.  
  686. pascal void WEFindWord(SInt32 offset, WEEdge edge, SInt32 *wordStart, SInt32 *wordEnd, WEHandle hWE)
  687. {
  688.     WEPtr pWE;
  689.     ScriptCode script;
  690.     SInt32 contextStart, contextEnd;
  691.     Handle hText;
  692.     OffsetTable wordBreaks;
  693.     Boolean saveTextLock, saveWELock;
  694.  
  695.     // lock the WE record
  696.     saveWELock = _WESetHandleLock((Handle) hWE, true);
  697.     pWE = *hWE;
  698.  
  699.     // find a script context containing the specified offset
  700.     // (words cannot straddle script boundaries)
  701.     script = _WEGetContext(offset, &contextStart, &contextEnd, hWE);
  702.  
  703.     // lock the text
  704.     hText = pWE->hText;
  705.     saveTextLock = _WESetHandleLock(hText, true);
  706.  
  707.     // call the word break hook
  708.     CallWEWordBreakProc(*hText + contextStart, contextEnd - contextStart,
  709.         offset - contextStart, edge, wordBreaks, script, hWE, pWE->wordBreakHook);
  710.  
  711.     // unlock the text
  712.     _WESetHandleLock(hText, saveTextLock);
  713.  
  714.     // calculate wordStart and wordEnd relative to the beginning of the text
  715.     if (wordStart != nil)
  716.     {
  717.         *wordStart = contextStart + wordBreaks[0].offFirst;
  718.     }
  719.     if (wordEnd != nil)
  720.     {
  721.         *wordEnd = contextStart + wordBreaks[0].offSecond;
  722.     }
  723. }
  724.  
  725. pascal SInt16 WECharByte(SInt32 offset, WEHandle hWE)
  726. {
  727.     WEPtr pWE;
  728.     Handle hText;
  729.     SInt32 contextStart, contextEnd;
  730.     ScriptCode script;
  731.     Boolean saveWELock, saveTextLock;
  732.     SInt16 retval = smSingleByte;
  733.  
  734.     // lock the WE record
  735.     saveWELock = _WESetHandleLock((Handle) hWE, true);
  736.     pWE = *hWE;
  737.  
  738.     // do nothing unless there is at least one double-byte script system installed
  739.     // and make sure offset is within allowed bounds
  740.     if (BTST(pWE->flags, weFDoubleByte))
  741.     {
  742.         if ((offset >= 0) && (offset < pWE->textLength))
  743.         {
  744.  
  745.             // find a script context containing the specified offset
  746.             script = _WEGetRestrictedContext(offset, &contextStart, &contextEnd, hWE);
  747.  
  748.             // lock the text
  749.             hText = pWE->hText;
  750.             saveTextLock = _WESetHandleLock(hText, true);
  751.  
  752.             // pass the CharByte hook a pointer to the beginning of the style run
  753.             retval = CallWECharByteProc(*hText + contextStart,
  754.                 offset - contextStart, script, hWE, pWE->charByteHook);
  755.  
  756.             // unlock the text
  757.             _WESetHandleLock(hText, saveTextLock);
  758.         }
  759.     }
  760.  
  761.     // unlock the WE record
  762.     _WESetHandleLock((Handle) hWE, saveWELock);
  763.  
  764.     return retval;
  765. }
  766.  
  767. pascal SInt16 WECharType(SInt32 offset, WEHandle hWE)
  768. {
  769.     WEPtr pWE;
  770.     Handle hText;
  771.     SInt32 contextStart, contextEnd;
  772.     ScriptCode script;
  773.     Boolean saveWELock, saveTextLock;
  774.     SInt16 retval = 0;
  775.  
  776.     // lock the WE record
  777.     saveWELock = _WESetHandleLock((Handle) hWE, true);
  778.     pWE = *hWE;
  779.  
  780.     // make sure offset is within allowed bounds
  781.     if ((offset >= 0) && (offset < pWE->textLength))
  782.     {
  783.  
  784.         // find a script context containing the specified offset
  785.         script = _WEGetRestrictedContext(offset, &contextStart, &contextEnd, hWE);
  786.  
  787.         // lock the text
  788.         hText = pWE->hText;
  789.         saveTextLock = _WESetHandleLock(hText, true);
  790.  
  791.         // pass the CharType hook a pointer to the beginning of the style run
  792.         retval = CallWECharTypeProc(*hText + contextStart,
  793.             offset - contextStart, script, hWE, pWE->charTypeHook);
  794.  
  795.         // unlock the text
  796.         _WESetHandleLock(hText, saveTextLock);
  797.     }
  798.  
  799.     // unlock the WE record
  800.     _WESetHandleLock((Handle) hWE, saveWELock);
  801.  
  802.     return retval;
  803. }
  804.  
  805. pascal void _WEGetCaretRect(SInt32 offset, SInt16 direction, Rect *caretRect, WEHandle hWE)
  806. {
  807.     LongPt thePoint;
  808.     SInt16 caretHeight;
  809.  
  810.     WEGetPoint(offset, direction, &thePoint, &caretHeight, hWE);
  811.     WELongPointToPoint(&thePoint, (Point *) &caretRect->top);
  812.     if (caretRect->left > (*hWE)->destRect.left)
  813.     {
  814.         caretRect->left--;
  815.     }
  816.  
  817.     caretRect->bottom = caretRect->top + caretHeight;
  818.     caretRect->right = caretRect->left + kCaretWidth;
  819. }
  820.  
  821. pascal void _WEDrawCaret(SInt32 offset, SInt16 direction, Boolean useDualCaret, WEHandle hWE)
  822. {
  823.     WEPtr pWE = *hWE;    // assume WE record is already locked
  824.     Rect caretRect;
  825.     GrafPtr savePort;
  826.     RgnHandle saveClip;
  827.  
  828.     // set up the port
  829.     GetPort(&savePort);
  830.     SetPort(pWE->port);
  831.  
  832.     // clip to the view region
  833.     saveClip = NewRgn();
  834.     GetClip(saveClip);
  835.     SetClip(pWE->viewRgn);
  836.  
  837.     // calculate caret rectangle for the primary caret
  838.     _WEGetCaretRect(offset, direction, &caretRect, hWE);
  839.  
  840.     // should we use a dual caret?
  841.     if (useDualCaret)
  842.     {
  843.         // draw the primary caret
  844.         caretRect.bottom = (caretRect.top + caretRect.bottom) >> 1;
  845.         InvertRect(&caretRect);
  846.  
  847.         // calculate the caret rectangle for the secondary caret
  848.         _WEGetCaretRect(offset, (leftCaret + rightCaret) - direction, &caretRect, hWE);
  849.         caretRect.top = (caretRect.top + caretRect.bottom) >> 1;
  850.     }
  851.  
  852.     // draw the caret (either a single caret, or the secondary caret)
  853.     InvertRect(&caretRect);
  854.  
  855.     // restore the clip region
  856.     SetClip(saveClip);
  857.     DisposeRgn(saveClip);
  858.  
  859.     // restore the port
  860.     SetPort(savePort);
  861. }
  862.  
  863. pascal void _WEBlinkCaret(WEHandle hWE)
  864. {
  865.     WEPtr pWE = *hWE;                // assume WE record is already locked
  866.     SInt16 direction = hilite;
  867.     Boolean useDualCaret = false;
  868.  
  869.     // do nothing if we're not active
  870.     if (!BTST(pWE->flags, weFActive))
  871.     {
  872.         return;
  873.     }
  874.  
  875. #if WASTE_NO_RO_CARET
  876.     if (BTST(pWE->features, weFReadOnly) && !BTST(pWE->flags, weFCaretVisible))
  877.     {
  878.         return;
  879.     }
  880. #endif
  881.  
  882.     if (BTST(pWE->flags, weFBidirectional))
  883.     {
  884.         // SPECIAL PROCESSING FOR BIDIRECTIONAL SCRIPTS
  885.         // check if we should use a dual caret
  886.         if (BTST(GetScriptManagerVariable(smGenFlags), smfDualCaret))
  887.         {
  888.             //    DUAL CARET
  889.             useDualCaret = true;
  890.  
  891.             //    primary caret position depends on primary line direction
  892.             direction = IsRightToLeft(pWE->direction) ? rightCaret : leftCaret;
  893.         }
  894.         else
  895.         {
  896.             //    SINGLE (JUMPING) CARET
  897.             //    caret position depends on keyscript direction
  898.             //    when erasing, though, use previously used direction
  899.             //    in case keyscript changed in the meantime
  900.             if (BTST(pWE->flags, weFCaretVisible))
  901.             {
  902.                 direction = BTST(pWE->flags, weFCaretRight) ? rightCaret : leftCaret;
  903.             }
  904.             else
  905.             {
  906.                 if (GetScriptVariable(GetScriptManagerVariable(smKeyScript), smScriptRight))
  907.                 {
  908.                     direction = rightCaret;
  909.                     BSET(pWE->flags, weFCaretRight);
  910.                 }
  911.                 else
  912.                 {
  913.                     direction = leftCaret;
  914.                     BCLR(pWE->flags, weFCaretRight);
  915.                 }
  916.             }
  917.         }
  918.     }
  919.  
  920.     // redraw the caret, in XOR mode
  921.     _WEDrawCaret(pWE->selStart, direction, useDualCaret, hWE);
  922.  
  923.     // keep track of the current caret visibility status
  924.     BCHG(pWE->flags, weFCaretVisible);    // invert flag
  925.  
  926.     // update caretTime
  927.     pWE->caretTime = TickCount();
  928. }
  929.  
  930. static Boolean SLCollectHiliteRgn
  931.     (
  932.         WELineRec *pLine,
  933.         const WERunAttributes *pAttrs,
  934.         Ptr pSegment,
  935.         SInt32 segmentStart,
  936.         SInt32 segmentLength,
  937.         JustStyleCode styleRunPosition,
  938.         WEHandle hWE,
  939.         void *callbackData
  940.     )
  941. // Get the hilite rgn for the current run, or the portion thereof within the hilite range;
  942. // this just does FrameRect for the relevant area(s) -- caller is assumed to have a region open
  943. {
  944.     WEPtr pWE = *hWE;
  945.     struct SLCollectHiliteRgnData *cd = (struct SLCollectHiliteRgnData *) callbackData;
  946.     SInt16 lineHeight;
  947.     SInt32 segmentEnd;
  948.     SInt32 firstOffset, secondOffset;
  949.     OffsetTable    offsets;
  950.     SInt16 i;
  951.     Rect r;
  952.     Fixed slop;
  953.     SInt16 width;
  954.     SInt32 start, end, lineOrigin;
  955.     WEDirection direction;
  956.     SInt32 offset;
  957.  
  958.     // determine the line direction
  959.     direction = pWE->direction;
  960.     if (direction == weDirDefault)
  961.     {
  962.         direction = GetSysDirection();
  963.     }
  964.  
  965.     // find the line origin & height
  966.     lineOrigin = pWE->destRect.top + pLine->lineOrigin;
  967.     lineHeight = pLine[1].lineOrigin - pLine[0].lineOrigin;
  968.  
  969.     // initialize cd->hPos on first segment of the line
  970.     if (IS_LEFTMOST_RUN(styleRunPosition))
  971.     {
  972.         cd->hPos = pWE->destRect.left + _WECalcPenIndent(pLine, pWE->alignment, direction);
  973.  
  974.         // if selection starts before beginning of line AND direction is LR,
  975.         // OR selection ends after end of line AND direction is RL,
  976.         // then we need to include the area to the left of the line in the hilite
  977.  
  978.         if ( ((cd->rangeStart < pLine[0].lineStart) && (direction == weDirLeftToRight))
  979.           || ((cd->rangeEnd >= pLine[1].lineStart) && (direction == weDirRightToLeft)) )
  980.         {
  981.             // frame the rect to the left of the actual text
  982.             SetRect(&r, pWE->destRect.left, lineOrigin, cd->hPos, lineOrigin + lineHeight);
  983.             FrameRect(&r);
  984.         }
  985.     }
  986.  
  987.     // calculate segment end
  988.     segmentEnd = segmentStart + segmentLength;
  989.  
  990. #if WASTE_OBJECTS
  991.     if (pAttrs->runStyle.tsObject != nil)
  992.     {
  993.         // EMBEDDED OBJECT
  994.  
  995.         // get object width
  996.         width = (*pAttrs->runStyle.tsObject)->objectSize.h;
  997.  
  998.         // if this object is hilited, add its rect to the region
  999.         if (cd->rangeStart <= segmentStart && cd->rangeEnd >= segmentEnd)
  1000.         {
  1001.             SetRect(&r, cd->hPos, lineOrigin, cd->hPos + width, lineOrigin + lineHeight);
  1002.             FrameRect(&r);
  1003.         }
  1004.     }
  1005.     else
  1006. #endif
  1007.     {
  1008.         // REGULAR TEXT
  1009.  
  1010.         // calculate slop value if justifying
  1011.         if (pWE->alignment == weJustify)
  1012.         {
  1013.             slop = FixMul(PortionLine(pSegment, segmentLength, styleRunPosition, kOneToOneScaling, kOneToOneScaling), pLine->lineJustAmount);
  1014.         }
  1015.         else
  1016.         {
  1017.             slop = 0;
  1018.         }
  1019.  
  1020.         // see whether this run includes any hilited text
  1021.         if (cd->rangeEnd > segmentStart && cd->rangeStart < segmentEnd)
  1022.         {
  1023.             if (segmentStart < cd->rangeStart)
  1024.             {
  1025.                 firstOffset = cd->rangeStart - segmentStart;
  1026.             }
  1027.             else
  1028.             {
  1029.                 firstOffset = 0;
  1030.             }
  1031.  
  1032.             if (segmentEnd >= cd->rangeEnd)
  1033.             {
  1034.                 secondOffset = cd->rangeEnd - segmentStart;
  1035.             }
  1036.             else
  1037.             {
  1038.                 secondOffset = segmentLength;
  1039.             }
  1040.  
  1041.             HiliteText(pSegment, segmentLength, firstOffset, secondOffset, offsets);
  1042.  
  1043.             for ( i = 0; i <= 2; ++i )
  1044.             {    // for each pair of offsets...
  1045.                 if (offsets[i].offFirst != offsets[i].offSecond)
  1046.                 {    // if offset pair is not empty...
  1047.                     // calculate horizontal coordinates of start and end of hilite area
  1048.                     start = CallWECharToPixelProc(pSegment, segmentLength,
  1049.                         slop, offsets[i].offFirst, hilite, styleRunPosition, cd->hPos,
  1050.                         hWE, pWE->charToPixelHook);
  1051.                     end = CallWECharToPixelProc(pSegment, segmentLength,
  1052.                         slop, offsets[i].offSecond, hilite, styleRunPosition, cd->hPos,
  1053.                         hWE, pWE->charToPixelHook);
  1054.                     // frame the rectangle to be hilited
  1055.                     SetRect(&r, cd->hPos + start, lineOrigin, cd->hPos + end, lineOrigin + lineHeight);
  1056.                     FrameRect(&r);
  1057.                 }    // if offset pair is not empty
  1058.             }    // for each pair of offsets
  1059.         }
  1060.  
  1061.         // calculate the width of this segment
  1062.         if (BTST(pAttrs->runStyle.tsFlags, tsRightToLeft) == 0)
  1063.         {
  1064.             i = leftCaret;
  1065.             offset = segmentLength;
  1066.         }
  1067.         else
  1068.         {
  1069.             i = rightCaret;
  1070.             offset = 0;
  1071.         }
  1072.         width = CallWECharToPixelProc(pSegment, segmentLength, slop,
  1073.             offset, i, styleRunPosition, cd->hPos, hWE, pWE->charToPixelHook);
  1074.     }
  1075.  
  1076.     // update cd->hPos to account for the width of the segment
  1077.     cd->hPos += width;
  1078.  
  1079.     if (IS_RIGHTMOST_RUN(styleRunPosition))
  1080.     {
  1081.         // if selection ends after end of line AND direction is LR,
  1082.         // OR selection begins before beginning of line AND direction is RL,
  1083.         // then we need to include the area to the right of the line in the hilite
  1084.  
  1085.         if ( (cd->rangeEnd >= pLine[1].lineStart && direction == weDirLeftToRight)
  1086.           || (cd->rangeStart < pLine->lineStart && direction == weDirRightToLeft) )
  1087.         {
  1088.             // frame the rect to the right of the text
  1089.             SetRect(&r, cd->hPos, lineOrigin, pWE->destRect.right, lineOrigin + lineHeight);
  1090.             FrameRect(&r);
  1091.         }
  1092.     }
  1093.  
  1094.     return false;    // don't break out of the segment loop
  1095. }
  1096.  
  1097. pascal RgnHandle WEGetHiliteRgn(SInt32 rangeStart, SInt32 rangeEnd, WEHandle hWE)
  1098. {
  1099.     // returns the hilite region corresponding to the specified range
  1100.     // the caller is responsible for disposing of the returned region
  1101.     // when it's finished with it
  1102.  
  1103.     WEPtr pWE;
  1104.     RgnHandle hiliteRgn;
  1105.     LongRect selRect;
  1106.     LongPt firstPoint, lastPoint;
  1107.     SInt32 lineIndex;
  1108.     SInt16 firstLineHeight, lastLineHeight;
  1109.     Rect r;
  1110.     GrafPtr savePort;
  1111.     Boolean saveWELock;
  1112.     Boolean bidirectional;
  1113.     struct SLCollectHiliteRgnData callbackData;
  1114.  
  1115.     // lock the WE record
  1116.     saveWELock = _WESetHandleLock((Handle) hWE, true);
  1117.     pWE = *hWE;
  1118.  
  1119.     // set up the port
  1120.     GetPort(&savePort);
  1121.     SetPort(pWE->port);
  1122.  
  1123.     // make sure rangeStart comes before rangeEnd
  1124.     _WEReorder(&rangeStart, &rangeEnd);
  1125.  
  1126.     // calculate pixel location corresponding to rangeStart
  1127.     WEGetPoint(rangeStart, hilite, &firstPoint, &firstLineHeight, hWE);
  1128.  
  1129.     // calculate pixel location corresponding to rangeEnd
  1130.     WEGetPoint(rangeEnd, hilite, &lastPoint, &lastLineHeight, hWE);
  1131.  
  1132.     // open a region: rects to be hilited will be accumulated in this
  1133.     OpenRgn();
  1134.  
  1135.     // any bidirectional scripts installed?
  1136.     bidirectional = (BTST(pWE->flags, weFBidirectional) != 0);
  1137.     if (bidirectional)
  1138.     {
  1139.         callbackData.rangeStart = rangeStart;
  1140.         callbackData.rangeEnd = rangeEnd;
  1141.     }
  1142.  
  1143.     if (firstPoint.v == lastPoint.v)
  1144.     {
  1145.         // selection range encompasses only one line
  1146.         // for bidirectional text, we need to loop through the style runs
  1147.         if (bidirectional)
  1148.         {
  1149.             lineIndex = WEOffsetToLine(rangeStart, hWE);
  1150.             _WESegmentLoop(lineIndex, lineIndex, SLCollectHiliteRgn, &callbackData, hWE);
  1151.         }
  1152.         else
  1153.         {
  1154.             WESetLongRect(&selRect, firstPoint.h, firstPoint.v, lastPoint.h, lastPoint.v + lastLineHeight);
  1155.             WELongRectToRect(&selRect, &r);
  1156.             FrameRect(&r);
  1157.         }
  1158.     }
  1159.     else
  1160.     {
  1161.         // selection range encompasses more than one line
  1162.         // hilite the first line
  1163.         if (bidirectional)
  1164.         {
  1165.             lineIndex = WEOffsetToLine(rangeStart, hWE);
  1166.             _WESegmentLoop(lineIndex, lineIndex, SLCollectHiliteRgn, &callbackData, hWE);
  1167.         }
  1168.         else
  1169.         {
  1170.             WESetLongRect(&selRect, firstPoint.h, firstPoint.v, pWE->destRect.right, firstPoint.v + firstLineHeight);
  1171.             WELongRectToRect(&selRect, &r);
  1172.             FrameRect(&r);
  1173.         }
  1174.  
  1175.         // any lines between the first and the last one?
  1176.         if (firstPoint.v + firstLineHeight < lastPoint.v)
  1177.         {
  1178.             // hilite all the lines in-between
  1179.             WESetLongRect(&selRect, pWE->destRect.left, firstPoint.v + firstLineHeight, pWE->destRect.right, lastPoint.v);
  1180.             WELongRectToRect(&selRect, &r);
  1181.             FrameRect(&r);
  1182.         }
  1183.  
  1184.         // hilite the last line
  1185.         if (bidirectional)
  1186.         {
  1187.             // ??? shouldn't this be WEOffsetToLine(rangeEnd - 1, hWE) ??? -- marco
  1188.             // ••• yes, I think you're right, but I haven't tested it thoroughly -- jonathan
  1189.             lineIndex = WEOffsetToLine(rangeEnd, hWE);
  1190.  
  1191.             // skip this if it's an empty last line of the document
  1192.             if ((rangeEnd < pWE->textLength) || (WEGetChar(rangeEnd - 1, hWE) != kEOL))
  1193.             {
  1194.                 _WESegmentLoop(lineIndex, lineIndex, SLCollectHiliteRgn, &callbackData, hWE);
  1195.             }
  1196.         }
  1197.         else
  1198.         {
  1199.             WESetLongRect(&selRect, pWE->destRect.left, lastPoint.v, lastPoint.h, lastPoint.v + lastLineHeight);
  1200.             WELongRectToRect(&selRect, &r);
  1201.             FrameRect(&r);
  1202.         }
  1203.     }
  1204.  
  1205.     // copy the accumulated region into a new region
  1206.     hiliteRgn = NewRgn();
  1207.     CloseRgn(hiliteRgn);
  1208.  
  1209.     // restrict this region to the view region
  1210.     SectRgn(hiliteRgn, pWE->viewRgn, hiliteRgn);
  1211.  
  1212.     // restore the port
  1213.     SetPort(savePort);
  1214.  
  1215.     // unlock the WE record
  1216.     _WESetHandleLock((Handle) hWE, saveWELock);
  1217.  
  1218.     // return the hilite region
  1219.     return hiliteRgn;
  1220. }
  1221.  
  1222. pascal void _WEHiliteRange(SInt32 rangeStart, SInt32 rangeEnd, WEHandle hWE)
  1223. {
  1224.     WEPtr pWE;
  1225.     RgnHandle saveClip, auxRgn, hiliteRgn;
  1226.     PenState savePen;
  1227.     GrafPtr savePort;
  1228.  
  1229.     // the WE record must be already locked
  1230.     pWE = *hWE;
  1231.  
  1232.     // do nothing if the specified range is empty
  1233.     if (rangeStart == rangeEnd)
  1234.     {
  1235.         return;
  1236.     }
  1237.  
  1238.     // set up the port
  1239.     GetPort(&savePort);
  1240.     SetPort(pWE->port);
  1241.  
  1242.     // create auxiliary regions
  1243.     saveClip = NewRgn();
  1244.     auxRgn = NewRgn();
  1245.  
  1246.     // restrict the clip region to the view rectangle
  1247.     GetClip(saveClip);
  1248.     SectRgn(saveClip, pWE->viewRgn, auxRgn);
  1249.     SetClip(auxRgn);
  1250.  
  1251.     // get the hilite region corresponding to the specified range
  1252.     hiliteRgn = WEGetHiliteRgn(rangeStart, rangeEnd, hWE);
  1253.  
  1254.     // hilite the region or frame it, depending on the setting of the active flag
  1255.     if (BTST(pWE->flags, weFActive))
  1256.     {
  1257.         _WEClearHiliteBit();
  1258.         InvertRgn(hiliteRgn);
  1259.     }
  1260.     else if (BTST(pWE->features, weFOutlineHilite))
  1261.     {
  1262.         GetPenState(&savePen);
  1263.         PenNormal();
  1264.         PenMode(patXor);
  1265.         _WEClearHiliteBit();
  1266.         FrameRgn(hiliteRgn);
  1267.         SetPenState(&savePen);
  1268.     }
  1269.  
  1270.     // restore the clip region
  1271.     SetClip(saveClip);
  1272.  
  1273.     // dispose of all regions
  1274.     DisposeRgn(saveClip);
  1275.     DisposeRgn(auxRgn);
  1276.     DisposeRgn(hiliteRgn);
  1277.  
  1278.     // restore the port
  1279.     SetPort(savePort);
  1280. }
  1281.  
  1282. pascal void WESetSelection(SInt32 selStart, SInt32 selEnd, WEHandle hWE)
  1283. {
  1284.     WEPtr pWE;
  1285.     SInt32 oldSelStart, oldSelEnd;
  1286.     Boolean saveWELock;
  1287.  
  1288.     // lock the WE record
  1289.     saveWELock = _WESetHandleLock((Handle) hWE, true);
  1290.     pWE = *hWE;
  1291.  
  1292.     // range-check parameters
  1293.     selStart = _WEPinInRange(selStart, 0, pWE->textLength);
  1294.     selEnd = _WEPinInRange(selEnd, 0, pWE->textLength);
  1295.  
  1296.     // set the weFAnchorIsEnd bit if selStart > selEnd,  reorder the endpoints
  1297.     if (selStart > selEnd)
  1298.     {
  1299.         BSET(pWE->flags, weFAnchorIsEnd);
  1300.         _WEReorder(&selStart, &selEnd);
  1301.     }
  1302.     else
  1303.     {
  1304.         BCLR(pWE->flags, weFAnchorIsEnd);
  1305.     }
  1306.  
  1307.     // get old selection range
  1308.     oldSelStart = pWE->selStart;
  1309.     oldSelEnd = pWE->selEnd;
  1310.  
  1311.     // selection changed?
  1312.     if ((oldSelStart != selStart) || (oldSelEnd != selEnd))
  1313.     {
  1314.         // invalid the null style
  1315.         BCLR(pWE->flags, weFUseNullStyle);
  1316.  
  1317.         // hide the caret if it's showing
  1318.         if (BTST(pWE->flags, weFCaretVisible))
  1319.         {
  1320.             _WEBlinkCaret(hWE);
  1321.         }
  1322.  
  1323.         // set new selection range
  1324.         pWE->selStart = selStart;
  1325.         pWE->selEnd = selEnd;
  1326.  
  1327.         // skip this section if either recalc or redraw has been inhibited
  1328.         if (! (pWE->features & ((1L << weFInhibitRecal) | (1L << weFInhibitRedraw))))
  1329.         {
  1330.             // if we're active, invert the exclusive-OR between the old range and the new range.
  1331.             // if we're inactive, this optimization can't be used because of outline highlighting.
  1332.             if (BTST(pWE->flags, weFActive))
  1333.             {
  1334.                 _WEReorder(&oldSelStart, &selStart);
  1335.                 _WEReorder(&oldSelEnd, &selEnd);
  1336.                 _WEReorder(&oldSelEnd, &selStart);
  1337.             }
  1338.  
  1339.             _WEHiliteRange(oldSelStart, oldSelEnd, hWE);
  1340.             _WEHiliteRange(selStart, selEnd, hWE);
  1341.  
  1342.             if (!BTST(pWE->flags, weFMouseTracking))
  1343.             {
  1344.                 // redraw the caret immediately, if the selection range is empty
  1345.                 if (pWE->selStart == pWE->selEnd)
  1346.                 {
  1347.                     _WEBlinkCaret(hWE);
  1348.                 }
  1349.  
  1350.                 // clear clickCount, unless we're tracking the mouse
  1351.                 pWE->clickCount = 0;
  1352.  
  1353.                 // scroll the selection into view, unless we're tracking the mouse
  1354.                 WESelView(hWE);
  1355.  
  1356.             }
  1357.         } // if redrawing not inhibited
  1358.     } // if selection changed
  1359.  
  1360.     // unlock the WE record
  1361.     _WESetHandleLock((Handle) hWE, saveWELock);
  1362. }
  1363.  
  1364. static Boolean SLCrossDirectionBoundary
  1365.     (
  1366.         WELineRec *pLine,
  1367.         const WERunAttributes *pAttrs,
  1368.         Ptr pSegment,
  1369.         SInt32 segmentStart,
  1370.         SInt32 segmentLength,
  1371.         JustStyleCode styleRunPosition,
  1372.         WEHandle hWE,
  1373.         void *callbackData
  1374.     )
  1375. {
  1376. #pragma unused(pLine, pAttrs, pSegment, hWE)
  1377.     struct SLCrossDirectionBoundaryData *cd = (struct SLCrossDirectionBoundaryData *) callbackData;
  1378.     Boolean isOldSegment = false;
  1379.  
  1380.     // have we reached the segment that contains the old offset?
  1381.     isOldSegment = (cd->oldOffset == segmentStart + segmentLength);
  1382.  
  1383.     if (isOldSegment)
  1384.     {
  1385.         if (cd->movingRight)
  1386.         {
  1387.             cd->isDone = true;    //    exit on next iteration
  1388.             return false;
  1389.         }
  1390.         else
  1391.         {
  1392.             if (IS_LEFTMOST_RUN(styleRunPosition))
  1393.             {
  1394.                 cd->newOffset = segmentStart;
  1395.             }
  1396.             return true;        //    exit now
  1397.         }
  1398.     }
  1399.  
  1400.     cd->newOffset = segmentStart + segmentLength;
  1401.  
  1402.     if ((! isOldSegment) && (cd->isDone))
  1403.     {
  1404.         //    we're in the segment to the right of old segment
  1405.         return true;
  1406.     }
  1407.  
  1408.     return false;    //    keep looping
  1409. }
  1410.  
  1411. pascal SInt32 _WECrossDirectionBoundary(SInt32 offset, Boolean movingRight, WEHandle hWE)
  1412. {
  1413.     struct SLCrossDirectionBoundaryData cd;
  1414.     SInt32 lineIndex = WEOffsetToLine(offset, hWE);
  1415.  
  1416.     cd.oldOffset = cd.newOffset = offset;
  1417.     cd.movingRight = movingRight;
  1418.     cd.isDone = false;
  1419.     _WESegmentLoop(lineIndex, lineIndex, SLCrossDirectionBoundary, &cd, hWE);
  1420.     return cd.newOffset;
  1421. }
  1422.  
  1423. pascal SInt32 _WEArrowOffset(SInt16 action, SInt32 offset, WEHandle hWE)
  1424. {
  1425.     // given an action code (corresponding to a modifiers + arrow key combo)
  1426.     // and an offset into the text, find the offset of the new caret position
  1427.  
  1428.     WEPtr pWE = *hWE;    //    assume the WE record is already locked
  1429.     LongPt thePoint;
  1430.     SInt32 rangeStart, rangeEnd;
  1431.     SInt16 lineHeight;
  1432.     SInt16 arrow;
  1433.     Boolean prevDir, nextDir;
  1434. #if WASTE_KURTHS_OPTION_ARROWS
  1435.     SInt16 cType;
  1436. #endif
  1437.  
  1438.     //    extract arrow portion from action parameter
  1439.     arrow = action & 0x0003;
  1440.  
  1441.     if (BTST(pWE->flags, weFBidirectional) && (arrow <= kGoRight))
  1442.     {
  1443.         //    bidirectional pre-processing for left/right arrow keys
  1444.         prevDir = WEGetRunDirection(offset - 1, hWE);
  1445.         nextDir = WEGetRunDirection(offset, hWE);
  1446.  
  1447.         //    determine whether offset is at a direction boundary
  1448.         if (prevDir != nextDir)
  1449.         {
  1450.             //    offset at direction boundary:
  1451.             //    determine whether we should cross the boundary
  1452.             if (prevDir != (arrow != kGoLeft))
  1453.             {
  1454.                 offset = _WECrossDirectionBoundary(offset, (arrow != kGoLeft), hWE);
  1455.             }
  1456.         } // at direction boundary
  1457.  
  1458.         if ((arrow == kGoLeft) ? prevDir : nextDir)
  1459.         {
  1460.             //    insertion point is within a RL run: invert left/right keys
  1461.             arrow = (kGoLeft + kGoRight) - arrow;
  1462.             action = (action & ~0x0003) | arrow;
  1463.         }
  1464.     }    //    bidirectional pre-processing
  1465.  
  1466.     switch (action)
  1467.     {
  1468.         case kGoLeft:
  1469.         {
  1470.             if (offset > 0)
  1471.             {
  1472.                 offset--;
  1473.                 if (WECharByte(offset, hWE) != smSingleByte)
  1474.                 {
  1475.                     offset--;
  1476.                 }
  1477.             }
  1478.             break;
  1479.         }
  1480.  
  1481.         case kGoRight:
  1482.         {
  1483.             if (offset < pWE->textLength)
  1484.             {
  1485.                 if (WECharByte(offset, hWE) != smSingleByte)
  1486.                 {
  1487.                     offset++;
  1488.                 }
  1489.                 offset++;
  1490.             }
  1491.             break;
  1492.         }
  1493.  
  1494.         case kGoUp:
  1495.         {
  1496.             WEGetPoint(offset, hilite, &thePoint, nil, hWE);
  1497.             thePoint.v--;
  1498.             offset = WEGetOffset(&thePoint, nil, hWE);
  1499.             break;
  1500.         }
  1501.  
  1502.         case kGoDown:
  1503.         {
  1504.             WEGetPoint(offset, hilite, &thePoint, &lineHeight, hWE);
  1505.             thePoint.v += lineHeight;
  1506.             offset = WEGetOffset(&thePoint, nil, hWE);
  1507.             break;
  1508.         }
  1509.  
  1510.         case kGoWordStart:
  1511.         {
  1512. #if WASTE_KURTHS_OPTION_ARROWS
  1513.             // loop "forever" (until we break out of it)
  1514.             while (true)
  1515.             {
  1516.                 WEFindWord(offset, kTrailingEdge, &rangeStart, &rangeEnd, hWE);
  1517.                 offset = rangeStart;
  1518.  
  1519.                 // If the found range is empty, get outta here.  (Most
  1520.                 // likely this means that we have reached the beginning
  1521.                 // of the text.)
  1522.                 if (rangeStart == rangeEnd)
  1523.                 {
  1524.                     break;
  1525.                 }
  1526.  
  1527.                 cType = WECharType(rangeStart, hWE);
  1528.  
  1529.                 // If the char is punctuation (other than a number),
  1530.                 // it's not really a word, so keep looping.  Otherwise
  1531.                 // we're done.
  1532.                 if (((cType & smcTypeMask) != smCharPunct) ||
  1533.                     ((cType & smcClassMask) == smPunctNumber))
  1534.                 {
  1535.                     break;
  1536.                 }
  1537.             }
  1538. #else
  1539.             WEFindWord(offset, kTrailingEdge, &rangeStart, &rangeEnd, hWE);
  1540.             offset = rangeStart;
  1541. #endif // WASTE_KURTHS_OPTION_ARROWS
  1542.             break;
  1543.         }
  1544.  
  1545.         case kGoWordEnd:
  1546.         {
  1547. #if WASTE_KURTHS_OPTION_ARROWS
  1548.             // loop "forever" (until we break out of it)
  1549.             while (true)
  1550.             {
  1551.                 WEFindWord(offset, kLeadingEdge, &rangeStart, &rangeEnd, hWE);
  1552.                 offset = rangeEnd;
  1553.  
  1554.                 // If the found range is empty, get outta here.
  1555.                 // (Most likely this means that we have reached
  1556.                 // the end of the text.)
  1557.                 if (rangeStart == rangeEnd)
  1558.                 {
  1559.                     break;
  1560.                 }
  1561.  
  1562.                 // `rangeEnd - 1' may point in the middle of a two-byte
  1563.                 // character; that's ok, CharType can deal with that.
  1564.                 cType = WECharType(rangeEnd - 1, hWE);
  1565.  
  1566.                 // If the char is punctuation (other than a number),
  1567.                 // it's not really a word, so keep looping.  Otherwise
  1568.                 // we're done.
  1569.                 if (((cType & smcTypeMask) != smCharPunct) ||
  1570.                     ((cType & smcClassMask) == smPunctNumber))
  1571.                 {
  1572.                     break;
  1573.                 }
  1574.             }
  1575. #else
  1576.             WEFindWord(offset, kLeadingEdge, &rangeStart, &rangeEnd, hWE);
  1577.             offset = rangeEnd;
  1578. #endif // WASTE_KURTHS_OPTION_ARROWS
  1579.             break;
  1580.         }
  1581.  
  1582.         case kGoTextStart:
  1583.         {
  1584.             offset = 0;
  1585.             break;
  1586.         }
  1587.  
  1588.         case kGoTextEnd:
  1589.         {
  1590.             offset = pWE->textLength;
  1591.             break;
  1592.         }
  1593.  
  1594.         case kGoLineStart:
  1595.         {
  1596.             WEFindLine(offset, kLeadingEdge, &rangeStart, &rangeEnd, hWE);
  1597.             offset = rangeStart;
  1598.             break;
  1599.         }
  1600.  
  1601.         case kGoLineEnd:
  1602.         {
  1603.             WEFindLine(offset, kTrailingEdge, &rangeStart, &rangeEnd, hWE);
  1604.             offset = rangeEnd;
  1605.             if (offset < pWE->textLength)
  1606.             {
  1607.                 offset--;
  1608.                 if (WECharByte(offset, hWE) != smSingleByte)
  1609.                 {
  1610.                     offset--;
  1611.                 }
  1612.             }
  1613.             break;
  1614.         }
  1615.  
  1616.         default:
  1617.         {
  1618.             break;
  1619.         }
  1620.     }
  1621.  
  1622.     if (BTST(pWE->flags, weFBidirectional) && (arrow <= kGoRight))
  1623.     {
  1624.         //    bidirectional post-processing for left/right arrow keys
  1625.         prevDir = WEGetRunDirection(offset - 1, hWE);
  1626.         nextDir = WEGetRunDirection(offset, hWE);
  1627.  
  1628.         //    determine whether offset is at a direction boundary
  1629.         //    and whether we should cross it
  1630.         if ((prevDir != nextDir) && (prevDir != (arrow == kGoLeft)))
  1631.         {
  1632.             offset = _WECrossDirectionBoundary(offset, (arrow == kGoLeft), hWE);
  1633.         }
  1634.     } // bidirectional post-processing
  1635.  
  1636.     return offset;
  1637. }
  1638.  
  1639. pascal void _WEDoArrowKey (SInt16 arrow, EventModifiers modifiers, WEHandle hWE)
  1640. {
  1641.     // this routine is called by WEKey to handle arrow keys
  1642.  
  1643.     WEPtr pWE = *hWE;    // assume the WE record is already locked
  1644.     SInt16 action;
  1645.     SInt32 selStart, selEnd;
  1646.     SInt32 caretLoc, anchor;
  1647.  
  1648.     // calculate the "action" parameter for _WEArrowOffset from arrow and modifiers
  1649.     action = arrow - kArrowLeft;            // possible range: 0..3
  1650.     if (modifiers & optionKey)
  1651.     {
  1652.         action += kOption;
  1653.     }
  1654.     if (modifiers & cmdKey)
  1655.     {
  1656.         action += kCommand;
  1657.     }
  1658.  
  1659.     // get selection range
  1660.     selStart = pWE->selStart;
  1661.     selEnd = pWE->selEnd;
  1662.  
  1663.     if ((modifiers & shiftKey) == 0)
  1664.     {
  1665.         // if selection range isn't empty, collapse it to one of the endpoints
  1666.         if (selStart < selEnd)
  1667.         {
  1668.             if ((arrow == kArrowLeft) || (arrow == kArrowUp))
  1669.             {
  1670.                 caretLoc = selStart;
  1671.             }
  1672.             else
  1673.             {
  1674.                 caretLoc = selEnd;
  1675.             }
  1676.         }
  1677.         else
  1678.         {
  1679.             // otherwise move the insertion point
  1680.             caretLoc = _WEArrowOffset(action, selStart, hWE);
  1681.         }
  1682.  
  1683.         // set anchor to caretLoc, so new selection will be empty
  1684.         anchor = caretLoc;
  1685.     }
  1686.     else
  1687.     {
  1688.         // shift key was held down: extend the selection rather than replacing it
  1689.         // find out which selection boundary is the anchor and which is the free endpoint
  1690.         if (BTST(pWE->flags, weFAnchorIsEnd))
  1691.         {
  1692.             anchor = selEnd;
  1693.             caretLoc = selStart;
  1694.         }
  1695.         else
  1696.         {
  1697.             anchor = selStart;
  1698.             caretLoc = selEnd;
  1699.         }
  1700.  
  1701.         // move the free endpoint
  1702.         caretLoc = _WEArrowOffset(action, caretLoc, hWE);
  1703.     }
  1704.  
  1705.     // select the new selection
  1706.     WESetSelection(anchor, caretLoc, hWE);
  1707. }
  1708.  
  1709. pascal Boolean WEAdjustCursor(Point mouseLoc, RgnHandle mouseRgn, WEHandle hWE)
  1710. {
  1711.     // Call WEAdjustCursor to set the cursor shape when the mouse is in the view rectangle.
  1712.     // MouseRgn should be either a valid region handle or nil.
  1713.     // If mouseRgn is supplied (i.e., if it's not nil), it is intersected with a region
  1714.     // in global coordinates within which the cursor is to retain its shape.
  1715.     // WEAdjustCursor returns true if the cursor has been set.
  1716.     // Your application should set the cursor only if WEAdjustCursor returns false.
  1717.  
  1718.     WEPtr pWE;
  1719.     RgnHandle auxRgn, hiliteRgn;
  1720.     enum { kIBeam, kArrow} cursorType;
  1721.     Point portDelta;
  1722.     GrafPtr savePort;
  1723.     Boolean saveWELock;
  1724.     Boolean adjustCursor;
  1725.  
  1726.     adjustCursor = false;
  1727.     cursorType = kIBeam;
  1728.  
  1729.     // lock the WE record
  1730.     saveWELock = _WESetHandleLock((Handle) hWE, true);
  1731.     pWE = *hWE;
  1732.  
  1733.     // set up the port
  1734.     GetPort(&savePort);
  1735.     SetPort(pWE->port);
  1736.  
  1737.     // calculate delta between the local coordinate system and the global one
  1738.     portDelta.v = 0;
  1739.     portDelta.h = 0;
  1740.     LocalToGlobal(&portDelta);
  1741.  
  1742.     // calculate the visible portion of the view rectangle, in global coordinates
  1743.     auxRgn = NewRgn();
  1744.     CopyRgn(pWE->viewRgn, auxRgn);
  1745.     SectRgn(auxRgn, pWE->port->visRgn, auxRgn);
  1746.     OffsetRgn(auxRgn, portDelta.h, portDelta.v);
  1747.  
  1748.     if (PtInRgn(mouseLoc, auxRgn))
  1749.     {
  1750.         // mouse is within view rectangle: it's up to us to set the cursor
  1751.         adjustCursor = true;
  1752.  
  1753.         // if drag-and-drop is enabled, see if the mouse is within current selection
  1754.         if (BTST(pWE->flags, weFHasDragManager) && BTST(pWE->features, weFDragAndDrop))
  1755.         {
  1756.             if (pWE->selStart < pWE->selEnd)
  1757.             {
  1758.  
  1759.                 // get current hilite region in global coordinates
  1760.                 hiliteRgn = WEGetHiliteRgn(pWE->selStart, pWE->selEnd, hWE);
  1761.                 OffsetRgn(hiliteRgn, portDelta.h, portDelta.v);
  1762.  
  1763.                 // if mouse is within selection, set cursor to an arrow, else to an I-beam
  1764.                 // (actually, we still use an I-beam if less than GetDoubleClickTime() ticks have
  1765.                 // elapsed since the last mouse click, so that the cursor doesn't turn into an
  1766.                 // arrow while triple-clicking + dragging a range of lines)
  1767.  
  1768.                 if (PtInRgn(mouseLoc, hiliteRgn) && ((TickCount() > pWE->clickTime + GetDoubleClickTime()) ||
  1769.                     (pWE->clickEdge == kObjectEdge)))
  1770.                 {
  1771.                     cursorType = kArrow;                // use arrow cursor
  1772.                     CopyRgn(hiliteRgn, auxRgn);
  1773.                 }
  1774.                 else
  1775.                 {
  1776.                     DiffRgn(auxRgn, hiliteRgn, auxRgn);
  1777.                 }
  1778.  
  1779.                 // dispose of the hilite region
  1780.                 DisposeRgn(hiliteRgn);
  1781.  
  1782.             } // if drag-and-drop is enabled
  1783.         }
  1784.  
  1785.         // set the cursor
  1786.         if (cursorType == kIBeam)
  1787.         {
  1788.             SetCursor(*GetCursor(iBeamCursor));
  1789.         }
  1790.         else
  1791.         {
  1792.             SetCursor(&qd.arrow);
  1793.         }
  1794.  
  1795.         // set mouseRgn, if provided
  1796.         if (mouseRgn != nil)
  1797.         {
  1798.             SectRgn(mouseRgn, auxRgn, mouseRgn);
  1799.         }
  1800.     }
  1801.     else
  1802.     {
  1803.         // mouse is outside view rectangle: don't set the cursor; subtract viewRgn from mouseRgn
  1804.         if (mouseRgn != nil)
  1805.         {
  1806.             DiffRgn(mouseRgn, auxRgn, mouseRgn);
  1807.         }
  1808.     }
  1809.     // dispose of the temporary region
  1810.     DisposeRgn(auxRgn);
  1811.  
  1812.     // restore the port
  1813.     SetPort(savePort);
  1814.  
  1815.     // unlock the WE record
  1816.     _WESetHandleLock((Handle) hWE, saveWELock);
  1817.  
  1818.     return adjustCursor;
  1819. }
  1820.  
  1821. pascal void WEIdle(UInt32 *maxSleep, WEHandle hWE)
  1822. {
  1823.     WEPtr pWE;
  1824.     UInt32 currentTime, blinkTime, sleep;
  1825.     Boolean saveWELock;
  1826.  
  1827.     // lock the WE record
  1828.     saveWELock = _WESetHandleLock((Handle) hWE, true);
  1829.     pWE = *hWE;
  1830.  
  1831. #if WASTE_DEBUG
  1832.         _WESanityCheck(hWE);
  1833. #endif
  1834.  
  1835.     // the caret blinks only if we're active and the selection point is empty
  1836.     if (BTST(pWE->flags, weFActive) && (pWE->selStart == pWE->selEnd))
  1837.     {
  1838.         // get current time
  1839.         currentTime = TickCount();
  1840.  
  1841.         // calculate when the caret should be blinked again
  1842.         blinkTime = pWE->caretTime + GetCaretTime();
  1843.  
  1844.         if (currentTime < blinkTime)
  1845.         {
  1846.             sleep = blinkTime - currentTime;
  1847.         }
  1848.         else
  1849.         {
  1850.             _WEBlinkCaret(hWE);
  1851.             sleep = GetCaretTime();
  1852.         }
  1853.     }
  1854.     else
  1855.     {
  1856.         // if we don't need to blink the caret, we can sleep forever
  1857.         sleep = LONG_MAX;
  1858.     }
  1859.  
  1860.     // return sleepTime to the caller if maxSleep isn't nil
  1861.     if (maxSleep != nil)
  1862.     {
  1863.         *maxSleep = sleep;
  1864.     }
  1865.  
  1866.     // unlock the WE record
  1867.     _WESetHandleLock((Handle) hWE, saveWELock);
  1868. }
  1869.  
  1870. pascal void WEUpdate(RgnHandle updateRgn, WEHandle hWE)
  1871. {
  1872.     WEPtr pWE;
  1873.     LongRect updateRect;
  1874.     Rect r;
  1875.     RgnHandle saveClip, auxRgn;
  1876.     GrafPtr savePort;
  1877.     Boolean saveWELock;
  1878.  
  1879.     // lock the WE record
  1880.     saveWELock = _WESetHandleLock((Handle) hWE, true);
  1881.     pWE = *hWE;
  1882.  
  1883.     // set up the port
  1884.     GetPort(&savePort);
  1885.     SetPort(pWE->port);
  1886.  
  1887.     // save the clip region
  1888.     saveClip = NewRgn();
  1889.     GetClip(saveClip);
  1890.  
  1891.     // clip to the insersection between updateRgn and the view rectangle
  1892.     // (updateRgn may be nil; in this case, just clip to the view rectangle)
  1893.     auxRgn = NewRgn();
  1894.     if (updateRgn != nil)
  1895.     {
  1896.         SectRgn(updateRgn, pWE->viewRgn, auxRgn);
  1897.     }
  1898.     else
  1899.     {
  1900.         CopyRgn(pWE->viewRgn, auxRgn);
  1901.     }
  1902.     SetClip(auxRgn);
  1903.  
  1904.     if (!EmptyRgn(auxRgn))
  1905.     {
  1906.         // calculate the rectangle to update
  1907.         r = (*auxRgn)->rgnBBox;
  1908.         WERectToLongRect(&r, &updateRect);
  1909.  
  1910.         // find out which lines need to be redrawn and draw them
  1911.         // if updateRgn is nil, erase each line rectangle before redrawing
  1912.         _WEDrawLines( _WEPixelToLine(updateRect.top - pWE->destRect.top, hWE),
  1913.                       _WEPixelToLine((updateRect.bottom - 1) - pWE->destRect.top, hWE),
  1914.                       (updateRgn == nil), hWE);
  1915.  
  1916.         // erase the portion of the update rectangle below the last line (if any)
  1917.         updateRect.top = pWE->destRect.top + (*pWE->hLines)[pWE->nLines].lineOrigin;
  1918.         if (updateRect.top < updateRect.bottom)
  1919.         {
  1920.             WELongRectToRect(&updateRect, &r);
  1921.             CallWEEraseProc(&r, hWE, pWE->eraseHook);
  1922.         }
  1923.  
  1924.         // hilite the selection range or draw the caret (only if active)
  1925.         if (pWE->selStart < pWE->selEnd)
  1926.         {
  1927.             _WEHiliteRange(pWE->selStart, pWE->selEnd, hWE);
  1928.         }
  1929.         else if (BTST(pWE->flags, weFCaretVisible))
  1930.         {
  1931.             _WEBlinkCaret(hWE);
  1932.             BSET(pWE->flags, weFCaretVisible);
  1933.         }
  1934.     }
  1935.  
  1936.     DisposeRgn(auxRgn);
  1937.  
  1938.     // restore the clip region
  1939.     SetClip(saveClip);
  1940.     DisposeRgn(saveClip);
  1941.  
  1942.     // restore the port
  1943.     SetPort(savePort);
  1944.  
  1945.     // unlock the WE record
  1946.     _WESetHandleLock((Handle) hWE, saveWELock);
  1947. }
  1948.  
  1949. pascal void WEDeactivate(WEHandle hWE)
  1950. {
  1951.     WEPtr pWE;
  1952.     Boolean saveWELock;
  1953.  
  1954.     // lock the WE record
  1955.     saveWELock = _WESetHandleLock((Handle) hWE, true);
  1956.     pWE = *hWE;
  1957.  
  1958.     // do nothing if we are already inactive
  1959.     if (BTST(pWE->flags, weFActive))
  1960.     {
  1961.  
  1962.         // hide the selection range or the caret
  1963.         _WEHiliteRange(pWE->selStart, pWE->selEnd, hWE);
  1964.         if (BTST(pWE->flags, weFCaretVisible))
  1965.         {
  1966.             _WEBlinkCaret(hWE);
  1967.         }
  1968.  
  1969.         // clear the active flag
  1970.         BCLR(pWE->flags, weFActive);
  1971.  
  1972.         // frame the selection
  1973.         _WEHiliteRange(pWE->selStart, pWE->selEnd, hWE);
  1974.  
  1975.         // dispose of the offscreen graphics world, if any
  1976.         if (pWE->offscreenPort != nil)
  1977.         {
  1978.             DisposeGWorld((GWorldPtr)(pWE->offscreenPort));
  1979.             pWE->offscreenPort = nil;
  1980.         }
  1981.  
  1982.         // notify Text Services
  1983.         if (pWE->tsmReference != nil)
  1984.         {
  1985.             DeactivateTSMDocument(pWE->tsmReference);
  1986.         }
  1987.     }
  1988.  
  1989.     // unlock the WE record
  1990.     _WESetHandleLock((Handle) hWE, saveWELock);
  1991. }
  1992.  
  1993. pascal void WEActivate(WEHandle hWE)
  1994. {
  1995.     WEPtr pWE;
  1996.     Boolean saveWELock;
  1997.  
  1998.     if (WEIsActive(hWE)) return;
  1999.  
  2000.     // lock the WE record
  2001.     saveWELock = _WESetHandleLock((Handle) hWE, true);
  2002.     pWE = *hWE;
  2003.  
  2004.     // remove the selection frame
  2005.     _WEHiliteRange(pWE->selStart, pWE->selEnd, hWE);
  2006.  
  2007.     // set the active flag
  2008.     BSET(pWE->flags, weFActive);
  2009.  
  2010.     // show the selection range
  2011.     _WEHiliteRange(pWE->selStart, pWE->selEnd, hWE);
  2012.  
  2013.     // notify Text Services
  2014.     if (pWE->tsmReference != nil)
  2015.     {
  2016.         ActivateTSMDocument(pWE->tsmReference);
  2017.     }
  2018.  
  2019.     // unlock the WE record
  2020.     _WESetHandleLock((Handle) hWE, saveWELock);
  2021. }
  2022.  
  2023. pascal Boolean WEIsActive(WEHandle hWE)
  2024. {
  2025.     // return true iff the specified WE instance is currently active
  2026.     return BTST((*hWE)->flags, weFActive) ? true : false;
  2027. }
  2028.  
  2029. pascal void WEScroll(SInt32 hOffset, SInt32 vOffset, WEHandle hWE)
  2030. {
  2031.     WEPtr pWE;
  2032.     Rect viewRect;
  2033.     GrafPtr savePort;
  2034.     Boolean hideOutline, saveWELock;
  2035.  
  2036.     // do nothing if both scroll offsets are zero
  2037.     if ((hOffset == 0) && (vOffset == 0))
  2038.     {
  2039.         return;
  2040.     }
  2041.  
  2042.     // lock the WE record
  2043.     saveWELock = _WESetHandleLock((Handle) hWE, true);
  2044.     pWE = *hWE;
  2045.  
  2046.     // set up the port
  2047.     GetPort(&savePort);
  2048.     SetPort(pWE->port);
  2049.  
  2050.     // get view rect in short coordinates
  2051.     viewRect = (*pWE->viewRgn)->rgnBBox;
  2052.  
  2053.     // hide the caret if it's showing
  2054.     if (BTST(pWE->flags, weFCaretVisible))
  2055.     {
  2056.         _WEBlinkCaret(hWE);
  2057.     }
  2058.  
  2059.     // if we're inactive and outline highlighting is on, we have to temporarily
  2060.     // hide the selection outline while scrolling to avoid a cosmetic bug
  2061.     hideOutline = false;
  2062.     if (!BTST(pWE->flags, weFActive))
  2063.     {
  2064.         if (BTST(pWE->features, weFOutlineHilite))
  2065.         {
  2066.             hideOutline = true;
  2067.             _WEHiliteRange(pWE->selStart, pWE->selEnd, hWE);
  2068.             BCLR(pWE->features, weFOutlineHilite);
  2069.         }
  2070.     }
  2071.  
  2072.     // offset the destination rectangle by the specified amount
  2073.     WEOffsetLongRect(&pWE->destRect, hOffset, vOffset);
  2074.  
  2075.     // scroll the view rectangle
  2076.     // we use ScrollRect unless the whole text is to be redrawn anyway
  2077.     // notice that both ScrollRect and DragPreScroll take short (16-bit)
  2078.     // offset parameters, while WEScroll deals with long (32-bit) quantities
  2079.     if ((ABS(hOffset) < (viewRect.right - viewRect.left)) && (ABS(vOffset) < (viewRect.bottom - viewRect.top)))
  2080.     {
  2081.         RgnHandle updateRgn = NewRgn();
  2082.  
  2083.         // if we're currently tracking a drag, notify the Drag Manager we're about to scroll
  2084.         if (pWE->currentDrag != kNullDrag)
  2085.         {
  2086.             DragPreScroll(pWE->currentDrag, (SInt16) hOffset, (SInt16) vOffset);
  2087.         }
  2088.  
  2089.         // ScrollRect will set updateRgn to the region to redraw
  2090.         ScrollRect(&viewRect, (SInt16) hOffset, (SInt16) vOffset, updateRgn);
  2091.  
  2092.         if (pWE->currentDrag != kNullDrag)
  2093.         {
  2094.             DragPostScroll(pWE->currentDrag);
  2095.         }
  2096.  
  2097.         // redraw the exposed region
  2098.         WEUpdate(updateRgn, hWE);
  2099.         DisposeRgn(updateRgn);
  2100.     }
  2101.     else
  2102.     {
  2103.         // redraw the whole text
  2104.         WEUpdate(nil, hWE);
  2105.     }
  2106.  
  2107.     // redraw the selection outline, if hidden
  2108.     if (hideOutline)
  2109.     {
  2110.         BSET(pWE->features, weFOutlineHilite);
  2111.         _WEHiliteRange(pWE->selStart, pWE->selEnd, hWE);
  2112.     }
  2113.  
  2114.     // restore the port
  2115.     SetPort(savePort);
  2116.  
  2117.     // unlock the WE record
  2118.     _WESetHandleLock((Handle) hWE, saveWELock);
  2119. }
  2120.  
  2121. pascal void WEPinScroll(SInt32 hOffset, SInt32 vOffset, WEHandle hWE)
  2122. {
  2123.     WEPtr pWE = *hWE;    // we ain't gonna move memory
  2124.     SInt32 delta;
  2125.  
  2126.     if (vOffset > 0)
  2127.     {
  2128.         delta = pWE->viewRect.top - pWE->destRect.top;
  2129.  
  2130.         // if top of the destRect would be moved below top of the viewRect,
  2131.         // pin it to top of the viewRect
  2132.         if (vOffset > delta)
  2133.         {
  2134.             vOffset = delta;
  2135.         }
  2136.     }
  2137.     else if (vOffset < 0)
  2138.     {
  2139.         delta = pWE->viewRect.bottom - pWE->destRect.bottom;
  2140.  
  2141.         // if bottom of the destRect would be moved above bottom of the viewRect,
  2142.         // pin it to bottom of viewRect
  2143.         if (vOffset < delta)
  2144.         {
  2145.             vOffset = delta;
  2146.         }
  2147.     }
  2148.  
  2149.     WEScroll(hOffset, vOffset, hWE);
  2150. }
  2151.  
  2152. pascal Boolean _WEScrollIntoView (SInt32 offset, WEHandle hWE)
  2153. {
  2154.     WEPtr pWE = *hWE;
  2155.     LongPt thePoint;
  2156.     SInt16 lineHeight;
  2157.     SInt32 hScroll, vScroll, temp;
  2158.     Boolean centerVertically;
  2159.     Boolean retval = false;
  2160.  
  2161.     // do nothing if automatic scrolling is disabled
  2162.     if (BTST(pWE->features, weFAutoScroll))
  2163.     {
  2164.  
  2165.         // find the selection point
  2166.         WEGetPoint(offset, hilite, &thePoint, &lineHeight, hWE);
  2167.  
  2168.         // assume no scrolling is needed
  2169.         vScroll = 0;
  2170.         hScroll = 0;
  2171.         centerVertically = false;
  2172.  
  2173.         // determine if we need to scroll up
  2174.         temp = pWE->viewRect.top - thePoint.v;
  2175.         if (temp > 0)
  2176.         {
  2177.             //    find out if we're really close to scrolling up just one line
  2178.             if (temp <= lineHeight)
  2179.             {
  2180.                 vScroll = temp;    // scroll up just enough to make the top line flush
  2181.             }
  2182.             else
  2183.             {
  2184.                 centerVertically = true;
  2185.             }
  2186.         }
  2187.  
  2188.         //    determine if we need to scroll down
  2189.         temp = pWE->viewRect.bottom - (thePoint.v + lineHeight);
  2190.         if (temp < 0)
  2191.         {
  2192.             //    find out if we're really close to scrolling down just one line
  2193.             if (temp >= -lineHeight)
  2194.             {
  2195.                 vScroll = temp;    // scroll down just enough to make the bottom line flush
  2196.             }
  2197.             else
  2198.             {
  2199.                 centerVertically = true;
  2200.             }
  2201.         }
  2202.  
  2203.         if (centerVertically)
  2204.         {
  2205.             // calculate the amount of vertical scrolling needed to center the selection into view
  2206.             vScroll = ((pWE->viewRect.top + pWE->viewRect.bottom) >> 1) -
  2207.                         (thePoint.v + (lineHeight >> 1));
  2208.         }
  2209.  
  2210.         if (vScroll != 0)
  2211.         {
  2212.             // we'd like to superimpose the bottom margins of the dest/view rects, if possible
  2213.             temp = pWE->viewRect.bottom - pWE->destRect.bottom;
  2214.             if (temp > vScroll)
  2215.             {
  2216.                 vScroll = temp;
  2217.             }
  2218.             // but we also have to make sure the dest top isn't scrolled below the view top
  2219.             temp = pWE->viewRect.top - pWE->destRect.top;
  2220.             if (temp < vScroll)
  2221.             {
  2222.                 vScroll = temp;
  2223.             }
  2224.         }
  2225.  
  2226.         // determine if we need to scroll horizontally
  2227.         if ((thePoint.h - 1 < pWE->viewRect.left) || (thePoint.h >= pWE->viewRect.right))
  2228.         {
  2229.             // calculate the amount of horizontal scrolling needed to center the selection into view
  2230.             hScroll = ((pWE->viewRect.left + pWE->viewRect.right) >> 1) - thePoint.h;
  2231.  
  2232.             // we'd like to superimpose the right margins of the dest/view rects, if possible
  2233.             temp = pWE->viewRect.right - pWE->destRect.right;
  2234.             if (temp > hScroll)
  2235.             {
  2236.                 hScroll = temp;
  2237.             }
  2238.  
  2239.             // but we also have to make sure the dest left isn't scrolled to the right of the view left
  2240.             temp = pWE->viewRect.left - pWE->destRect.left;
  2241.             if (temp < hScroll)
  2242.             {
  2243.                 hScroll = temp;
  2244.             }
  2245.         }
  2246.  
  2247.         // scroll the text if necessary
  2248.         if ((vScroll != 0) || (hScroll != 0))
  2249.         {
  2250.             retval = true;
  2251.             WEScroll(hScroll, vScroll, hWE);
  2252.             BSET(pWE->flags, weFDestRectChanged);
  2253.         }
  2254.     }
  2255.  
  2256.     // notify our client of changes to the destination rectangle
  2257.     if (BTST(pWE->flags, weFDestRectChanged))
  2258.     {
  2259.         if (pWE->scrollProc != nil)
  2260.         {
  2261.             CallWEScrollProc(hWE, pWE->scrollProc);
  2262.         }
  2263.         BCLR(pWE->flags, weFDestRectChanged);
  2264.     }
  2265.  
  2266.     return retval;
  2267. }
  2268.  
  2269. pascal void WESelView(WEHandle hWE)
  2270. {
  2271.     WEPtr pWE;
  2272.     Boolean saveWELock;
  2273.  
  2274.     // lock the WE record
  2275.     saveWELock = _WESetHandleLock((Handle) hWE, true);
  2276.     pWE = *hWE;
  2277.  
  2278.     // scroll the free endpoint of the selection into view
  2279.     _WEScrollIntoView(BTST(pWE->flags, weFAnchorIsEnd) ? pWE->selStart : pWE->selEnd, hWE);
  2280.  
  2281.     // unlock the WE record
  2282.     _WESetHandleLock((Handle) hWE, saveWELock);
  2283. }
  2284.